/**
 * 
 */
package spl.army;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.MatchResult;

import spl.utils.GlobalLogger;

/**
 * Observer class
 * Has the main() function that interacts with the user;
 * The main function has an iteration, that in each step, will
 * get string from STDIN, and check if it starts with a valid
 * command. If it is, an ObserverCommand implemented class will parse
 * the command and execute it. 
 * @author avikam
 *
 */
@SuppressWarnings("serial")
public class Observer implements Runnable {
	private static interface ObserverCommand {
		public boolean runCommand(String cmd);
		public void doCommand(ArrayList<String> commandParams);
	}
	
	private static abstract class BasicCommand implements ObserverCommand {
		String commandPattern;

		BasicCommand(String cmdPattern) {
			this.commandPattern = cmdPattern;
		}
		@Override
		public boolean runCommand(String cmd) {
			ArrayList<String> commandParams = parseCommand(cmd);
			if (null == commandParams) return false;
			
			GlobalLogger.getLogger().info("Issuing user command: " + cmd);
			doCommand(commandParams);
			System.out.println("Done!");
			return true;
		}

		abstract public void doCommand(ArrayList<String> commandParams);
		
		private ArrayList<String> parseCommand(String cmd) {
		     Scanner s = new Scanner(cmd);
		     s.findInLine(this.commandPattern);
		     
		     // Return true if there was a match
		     try {
		    	 MatchResult matched = s.match();
		    	 ArrayList<String> ret = new ArrayList<String>();
		    	 // Starting from 1 to groupCount, just like docs/api/java/util/Scanner.html says...
		    	 for(int i=1; i <= matched.groupCount(); i++)
		    	 	if (!ret.add(matched.group(i)))
		    	 		return null;
	    		 return ret;
		     } catch (IllegalStateException e) {
		    	 s.close();
		    	 return null;
		     }
		}
	}
	
	private static class StopCommand extends BasicCommand {
		StopCommand() { super("stop\\s*(now)?"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			ChiefOfStaff.stop();
			if (null != commandParams.get(0))
				Observer.stop();

		}
	}
	private static class AddSergeantCommand extends BasicCommand {
		static final int NAME=0;
		static final int NUM_THREADS=1;
		static final int NUM_MISSIONS=2;
		static final int SKILLS=3;
		static final int WORK_HOURS=4;
		static final int PRIORITY_ORDER=5;
		// RegEx: addSergeant [name] [numOfThreads] [maxNumOfMissions], [skill1] [skillN],[workHours] [priorityOrder]
		// i.e: addSergeant rambo 3 10, sniper commando, 10 shortestMission
		AddSergeantCommand() { super("addSergeant\\s+(\\w+)\\s+(\\d+)\\s+(\\d+),\\s+((?:\\w+\\s*)+),\\s*(\\d+)\\s+(\\w+)"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			
			Sergeant s = new Sergeant(commandParams.get(NAME),
									commandParams.get(NUM_MISSIONS),
									commandParams.get(SKILLS),
									commandParams.get(WORK_HOURS),
									commandParams.get(PRIORITY_ORDER),
									commandParams.get(NUM_THREADS));
			
			ChiefOfStaff.addSergeant(s);
		}
	}

	private static class AddMissionCommand extends BasicCommand {
		static final int NAME=0;
		static final int SKILL=1;
		static final int TIME=2;
		static final int ITEMS=3;
		static final int PRE_REQ=4;
		// RegEx: addMission [name] [skill] [time], [item1Name] [item1Amount] [itemNName] [itemNAmount], [preRequisiteMission1] [preRequisiteMissionM]
		AddMissionCommand() { super("addMission\\s+(\\w+)\\s+(\\w+)\\s+(\\d+),\\s*((?:\\w+\\s+\\d+\\s*)+)(?:\\s*,\\s*((?:\\w+\\s?)+))?"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			Board.addMission(commandParams.get(NAME),
					commandParams.get(SKILL),
					commandParams.get(TIME),
					commandParams.get(PRE_REQ),
					commandParams.get(ITEMS));
		}
	}
	private static class AddItemCommand extends BasicCommand {
		static final int NAME=0;
		static final int AMOUNT=1;
		// RegEx: addItem [name] [amount]
		// i.e: addItem sniperRifle 30
		AddItemCommand() { super("addItem\\s+(\\w+)\\s+(\\d+)"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			Warehouse.addItem(commandParams.get(NAME), commandParams.get(AMOUNT));
		}
	}
	private static class CompleteMissionsCommand extends BasicCommand {
		CompleteMissionsCommand() { super("completeMissions"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			Board.reportCompleteMissions();
		}
	}
	private static class IncompleteMissionsCommand extends BasicCommand {
		IncompleteMissionsCommand() { super("incompleteMissions"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			Board.reportIncompleteMissions();
		}
	}
	private static class SergeantsCommand extends BasicCommand {
		SergeantsCommand() { super("sergeants"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			ChiefOfStaff.reportSergeants();
		}
	}
	private static class WarehouseCommand extends BasicCommand {
		WarehouseCommand() { super("warehouse"); }
		@Override
		public void doCommand(ArrayList<String> commandParams) {
			Warehouse.reportWarehouse();
		}
	}
	
	// Register all commands
	private final static HashMap<String, ObserverCommand> COMMANDS = new HashMap<String,ObserverCommand>() {
		{
			put("addSergeant", new AddSergeantCommand());
			put("addMission", new AddMissionCommand());
			put("addItem", new AddItemCommand());
			put("completeMissions", new CompleteMissionsCommand());
			put("incompleteMissions", new IncompleteMissionsCommand());
			put("sergeants", new SergeantsCommand());
			put("warehouse", new WarehouseCommand());
			put("stop", new StopCommand());
		}
	};
	
	private static boolean stopRunning = false;
	
	/**
	 * Switch of stopping flag
	 */
	public static void stop() {
		stopRunning = true;
	}
	
	/**
	 * parses the command and call the generic runCommand function
	 * @param cmd that user entered.
	 * @return true if command parsed successfully.
	 */
	public static boolean runCommand(String cmd){
		// Create a new thread that executes the command
		
		// Get command by Hash
		String funcName = cmd.split(" ")[0];
		ObserverCommand command = Observer.COMMANDS.get(funcName);
		// 		no command? - return false;
		if (command == null) return false;
		
		return command.runCommand(cmd);
	}

	@Override
	public void run() {
		Scanner inp = new Scanner(System.in);
		do {
			System.out.print(">>> ");
			if (inp.hasNext()) {
				String command = new String(inp.nextLine());
				if (!Observer.runCommand(command))
					System.err.println("\n>>! Invalide input");
			} else {
				return;
			}
		}
		while(!stopRunning);
				
		System.out.println("System has been terminated");		
	}	
}
